#ifndef NE_STL_INCLUDENE_STL_ATOMIC_H
#define NE_STL_INCLUDENE_STL_ATOMIC_H

#include "ne_stl_build.h"
#if defined(NE_STL_USE_CLANG) || defined(NE_STL_USE_GCC)
#define NE_STL_ATOMIC_USE_GCC_INTRINSICS
#elif defined(NE_STL_USE_MSVC)
#define NE_STL_ATOMIC_USE_MSVC_INTRINSICS
namespace {
namespace msvc_atomic {
template <typename T, std::size_t N = sizeof(T)>
struct interlocked {};

template <typename T>
struct interlocked<T, 1> {
    static inline T increment(T volatile* x) {
        int8_t old_val, new_val;
        do {
            old_val = static_cast<int8_t>(*x);
            new_val = old_val + static_cast<int8_t>(1);
        } while (_InterlockedCompareExchange8(reinterpret_cast<volatile int8_t*>(x), new_val, old_val) != old_val);
        return static_cast<T>(new_val);
    }

    static inline T decrement(T volatile* x) {
        int8_t old_val, new_val;
        do {
            old_val = static_cast<int8_t>(*x);
            new_val = old_val - static_cast<int8_t>(1);
        } while (_InterlockedCompareExchange8(reinterpret_cast<volatile int8_t*>(x), new_val, old_val) != old_val);
        return static_cast<T>(new_val);
    }

    static inline T compare_exchange(T volatile* x, const T new_val, const T expected_val) {
        return static_cast<T>(_InterlockedCompareExchange8(
            reinterpret_cast<volatile int8_t*>(x), static_cast<const int8_t>(new_val), static_cast<const int8_t>(expected_val)));
    }

    static inline T exchange(T volatile* x, const T new_val) {
        return static_cast<T>(_InterlockedExchange8(reinterpret_cast<volatile char*>(x), static_cast<const char>(new_val)));
    }
};

template <typename T>
struct interlocked<T, 2> {
    static inline T increment(T volatile* x) { return static_cast<T>(_InterlockedIncrement16(reinterpret_cast<volatile int16_t*>(x))); }

    static inline T decrement(T volatile* x) { return static_cast<T>(_InterlockedDecrement16(reinterpret_cast<volatile int16_t*>(x))); }

    static inline T compare_exchange(T volatile* x, const T new_val, const T expected_val) {
        return static_cast<T>(_InterlockedCompareExchange16(
            reinterpret_cast<volatile int16_t*>(x), static_cast<const int16_t>(new_val), static_cast<const int16_t>(expected_val)));
    }

    static inline T exchange(T volatile* x, const T new_val) {
        return static_cast<T>(_InterlockedExchange16(reinterpret_cast<volatile int16_t*>(x), static_cast<const int16_t>(new_val)));
    }
};

template <typename T>
struct interlocked<T, 4> {
    static inline T increment(T volatile* x) { return static_cast<T>(_InterlockedIncrement(reinterpret_cast<volatile long*>(x))); }

    static inline T decrement(T volatile* x) { return static_cast<T>(_InterlockedDecrement(reinterpret_cast<volatile long*>(x))); }

    static inline T compare_exchange(T volatile* x, const T new_val, const T expected_val) {
        return static_cast<T>(_InterlockedCompareExchange(
            reinterpret_cast<volatile long*>(x), static_cast<const long>(new_val), static_cast<const long>(expected_val)));
    }

    static inline T exchange(T volatile* x, const T new_val) {
        long ret = _InterlockedExchange(reinterpret_cast<volatile long*>(x), *((long*)((void*)(&new_val))));
        return *(T*)((void*)(&ret));
    }
};

template <typename T>
struct interlocked<T, 8> {
    static inline T increment(T volatile* x) {
#if defined(NE_STL_ARC_HCPU_64_BITS)
        return static_cast<T>(_InterlockedIncrement64(reinterpret_cast<volatile int64_t*>(x)));
#else
        int64_t old_val, new_val;
        do {
            old_val = static_cast<int64_t>(*x);
            new_val = old_val + static_cast<int64_t>(1);
        } while (_InterlockedCompareExchange64(reinterpret_cast<volatile int64_t*>(x), new_val, old_val) != old_val);
        return static_cast<T>(new_val);
#endif  // NE_STL_ARC_HCPU_64_BITS
    }

    static inline T decrement(T volatile* x) {
#if defined(NE_STL_ARC_HCPU_64_BITS)
        return static_cast<T>(_InterlockedDecrement64(reinterpret_cast<volatile int64_t*>(x)));
#else
        int64_t old_val, new_val;
        do {
            old_val = static_cast<int64_t>(*x);
            new_val = old_val - static_cast<int64_t>(1);
        } while (_InterlockedCompareExchange64(reinterpret_cast<volatile int64_t*>(x), new_val, old_val) != old_val);
        return static_cast<T>(new_val);
#endif  // NE_STL_ARC_HCPU_64_BITS
    }

    static inline T compare_exchange(T volatile* x, const T new_val, const T expected_val) {
        return reinterpret_cast<T>(_InterlockedCompareExchange64(
            reinterpret_cast<volatile int64_t*>(x), reinterpret_cast<const int64_t>(new_val), reinterpret_cast<const int64_t>(expected_val)));
    }

    static inline T exchange(T volatile* x, const T new_val) {
#if defined(NE_STL_ARC_HCPU_64_BITS)
        return reinterpret_cast<T>(_InterlockedExchange64(reinterpret_cast<volatile int64_t*>(x), reinterpret_cast<const int64_t>(new_val)));
#else
        int64_t old_val;
        do {
            old_val = static_cast<int64_t>(*x);
        } while (_InterlockedCompareExchange64(reinterpret_cast<volatile int64_t*>(x), new_val, old_val) != old_val);
        return static_cast<T>(old_val);
#endif  // NE_STL_ARC_HCPU_64_BITS
    }
};
}  // namespace msvc_atomic
}  // namespace
#else
#error Unsupported compiler / system.
#endif

namespace nstd {

template <class T>
class atomic {
public:
    static_assert(sizeof(T) == 1 || sizeof(T) == 2 || sizeof(T) == 4 || sizeof(T) == 8, "Only types of size 1, 2, 4 or 8 are supported");

    atomic()
        : value_(static_cast<T>(0)) {}

    explicit atomic(const T value)
        : value_(value) {}

    T operator++() {
#if defined(NE_STL_ATOMIC_USE_GCC_INTRINSICS)
        return __atomic_add_fetch(&value_, 1, __ATOMIC_SEQ_CST);
#elif defined(NE_STL_ATOMIC_USE_MSVC_INTRINSICS)
        return msvc_atomic::interlocked<T>::increment(&value_);
#else
        return ++value_;
#endif
    }

    T operator--() {
#if defined(NE_STL_ATOMIC_USE_GCC_INTRINSICS)
        return __atomic_sub_fetch(&value_, 1, __ATOMIC_SEQ_CST);
#elif defined(NE_STL_ATOMIC_USE_MSVC_INTRINSICS)
        return msvc_atomic::interlocked<T>::decrement(&value_);
#else
        return --value_;
#endif
    }

    bool compare_exchange(const T expected_val, const T new_val) {
#if defined(NE_STL_ATOMIC_USE_GCC_INTRINSICS)
        T e = expected_val;
        return __atomic_compare_exchange_n(&value_, &e, new_val, true, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST);
#elif defined(NE_STL_ATOMIC_USE_MSVC_INTRINSICS)
        const T old_val = msvc_atomic::interlocked<T>::compare_exchange(&value_, new_val, expected_val);
        return (old_val == expected_val);
#else
        T e = expected_val;
        return value_.compare_exchange_weak(e, new_val);
#endif
    }

    void store(const T new_val) {
#if defined(NE_STL_ATOMIC_USE_GCC_INTRINSICS)
        __atomic_store_n(&value_, new_val, __ATOMIC_SEQ_CST);
#elif defined(NE_STL_ATOMIC_USE_MSVC_INTRINSICS)
        (void)msvc_atomic::interlocked<T>::exchange(&value_, new_val);
#else
        value_.store(new_val);
#endif
    }

    T load() const {
#if defined(NE_STL_ATOMIC_USE_GCC_INTRINSICS)
        return __atomic_load_n(&value_, __ATOMIC_SEQ_CST);
#elif defined(NE_STL_ATOMIC_USE_MSVC_INTRINSICS)
        return value_;
#else
        return value_;
#endif
    }

    T exchange(const T new_val) {
#if defined(NE_STL_ATOMIC_USE_GCC_INTRINSICS)
        return __atomic_exchange_n(&value_, new_val, __ATOMIC_SEQ_CST);
#elif defined(NE_STL_ATOMIC_USE_MSVC_INTRINSICS)
        return msvc_atomic::interlocked<T>::exchange(&value_, new_val);
#else
        return value_.exchange(new_val);
#endif
    }

    T operator=(const T new_value) {
        store(new_value);
        return new_value;
    }

    // operator T() const {
    //  return load();
    //}
    operator const T() const { return load(); }

private:
#if defined(NE_STL_ATOMIC_USE_GCC_INTRINSICS) || defined(NE_STL_ATOMIC_USE_MSVC_INTRINSICS)
    volatile T value_;
#else
    std::atomic<T> value_;
#endif

    atomic& operator=(const atomic&) = delete;
};

typedef nstd::atomic<bool> atomic_bool;
typedef nstd::atomic<char> atomic_char;
typedef nstd::atomic<int8_t> atomic_schar;
typedef nstd::atomic<uint8_t> atomic_uchar;
typedef nstd::atomic<int16_t> atomic_short;
typedef nstd::atomic<uint16_t> atomic_ushort;
typedef nstd::atomic<int32_t> atomic_int;
typedef nstd::atomic<uint32_t> atomic_uint;
typedef nstd::atomic<int32_t> atomic_long;
typedef nstd::atomic<uint32_t> atomic_ulong;
typedef nstd::atomic<int64_t> atomic_llong;
typedef nstd::atomic<uint64_t> atomic_ullong;

}  // namespace nstd

#endif  // !NE_STL_INCLUDENE_STL_ATOMIC_H
